/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package jard.webshop.management;

import jard.webshop.jsfbeans.Cart;
import jard.webshop.nbp.Invoice;
import jard.webshop.nbp.InvoiceEntry;
import jard.webshop.nbp.Order;
import jard.webshop.nbp.Payment;
import jard.webshop.nbp.Product;
import jard.webshop.nbp.Receipt;
import jard.webshop.nbp.Shipment;
import jard.webshop.nbp.User;
import jard.webshop.util.Constants;
import jard.webshop.util.FinancialException;
import jard.webshop.util.ProductException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.NoResultException;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;

/**
 *
 * @author CJP
 */
public class OrderManagement {

    private static EntityManagerFactory emf = null;
    
    public static void setEntityManagerFactory(EntityManagerFactory givenEmf) {
        emf = givenEmf;
    }
    
    private static OrderManagement om = null;

    public static OrderManagement getInstance() {
        if(om == null) {
            om = new OrderManagement();
        }
        
        return om;
    }
    
    private UserManagement um = null;
    
    private OrderManagement() {
        UserManagement.setEntityManagerFactory(emf);
        um = UserManagement.getInstance();
    }

    // As noted, JPA transactionality leaves a lot to be desired. For instance,
    // two threads with an EntityManager each can modify DB concurrently without warning.
    public synchronized Order addOrder(User user, Cart cart) throws FinancialException, ProductException {
        EntityManager em = emf.createEntityManager();
        user = um.getUser(user.getEmailAddress());
        System.out.println("Processing order for user => " + user);
        if (user.hasPaidAllBills() == false) {
            throw new FinancialException("User has too many unpaid previous bills.");
        }

        Map<Product, Integer> products = cart.getProducts();
        

        EntityTransaction tx = em.getTransaction();
        System.out.println("User => " + um.getUser(user.getEmailAddress()));
        tx.begin();
        
        Order order = new Order(user);
        Invoice invoice = new Invoice(user);
        for (Map.Entry<Product, Integer> entry : products.entrySet()) {
            Product product = entry.getKey();
            Integer quantity = entry.getValue();
            Integer newStock = product.getStock() - quantity;
            if (quantity > 0 && newStock >= 0) { // simpify after adding validation to input forms?
                // When money is involved we should double-check
                // TODO: Here we should see if employee discount is greater
                // than the normal discount, in which case the employee discount
                // is used
                Float actualPrice = product.getPrice() - product.getDiscount();
                if (0 < actualPrice && actualPrice <= product.getPrice()) {
                    product.setStock(newStock);
                    em.merge(product);
                    Float totalItemCost = quantity*actualPrice;
                    //sellPrices.put(product, totalItemCost);
                    InvoiceEntry invoiceEntry = new InvoiceEntry(product, quantity, actualPrice, totalItemCost);
                    if(invoiceEntry == null) {
                        System.out.println("Darned, InvoiceEntry for " + product + " as null!");
                    } else {
                        System.out.println("Good, InvoiceEntry for " + product + " was not null.");
                    }
                    invoice.addEntry(invoiceEntry);
                } else {
                    throw new FinancialException("Sorry, but the calculated price"
                            + " was not in the right interval: " + actualPrice);
                }
            } else {
                tx.rollback();
                throw new ProductException("Tried to order more of a product than was in stock.");
            }
        }
        
        order.setProducts(products);
        order.setInvoice(invoice);
        em.persist(order);
        
        
        tx.commit();
        
        
        tx = em.getTransaction();
        tx.begin();
        user.addOrder(order);
        sendShipment(order);

        em.merge(user);
        //em.merge(order);
        tx.commit();
        
        return order;
    }
    
    public synchronized Receipt registerPayment(Invoice invoice, Float amount) throws FinancialException {
        Payment payment = new Payment(invoice, amount);
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        
        tx.begin();
        
        invoice.addPayment(payment);
        em.merge(invoice);
        Receipt rcpt = null;
        if(invoice.isPaid()) {
            rcpt = new Receipt(invoice);
            em.persist(rcpt);
        }
        tx.commit();

        return rcpt;
    }
    
    public Invoice getInvoice(Invoice invoice) {
        EntityManager em = emf.createEntityManager();
        
        return em.find(Invoice.class, invoice.getId());
    }
    
    private synchronized Shipment sendShipment(Order order) {
        Float totalWeight = 0f;
        for(Map.Entry<Product, Integer> entry : order.getProducts().entrySet()) {
            Product product = entry.getKey();
            Integer quantity = entry.getValue();
            totalWeight += product.getWeight()*quantity;
        }
        Shipment shipment = new Shipment(order, totalWeight);
        order.setShipment(shipment);
        
        return shipment;
    }
    
    public List<Order> getAllOrders() {
        EntityManager em = emf.createEntityManager();
        TypedQuery<Order> query = em.createNamedQuery(Constants.GET_ALL_ORDERS, Order.class);
        try {
            List<Order> retVal = query.getResultList();
            return retVal;
        } catch(NoResultException e) {
            return new ArrayList<Order>();
        }
    }
    
    public List<Receipt> getAllReceipts() {
        EntityManager em = emf.createEntityManager();
        TypedQuery<Receipt> query = em.createNamedQuery(Constants.GET_ALL_RCPTS, Receipt.class);
        try {
            List<Receipt> retVal = query.getResultList();
            return retVal;
        } catch(NoResultException e) {
            return new ArrayList<Receipt>();
        }
    }
}
